﻿using Jacobi.Vst.Framework;
using Jacobi.Vst.Core;
using System;
using System.Configuration;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Aleator.Resources;
using Aleator.Core.Techniques;
using Aleator.Helpers;

namespace Aleator.Core
{
    /// <summary>
    /// Phrase class for drum patterns
    /// </summary>
    public class DrumPhrase : ConcretePhrase
    {
        #region Members

        //Modulos used for placing drum note remainders
        private int m_kickMod;
        private int m_snareMod;
        private int m_highHatMod;

        //Values indicating what a quarter note is divided by to give you the smallest possible value for each drum.
        //1 = quarters, 2 = 8ths, 4 = 16ths, 8 = 32nds. Basically the number of possible note plays per quarter note
        private int m_kickResolution;
        private int m_snareResolution;
        private int m_highHatResolution;

        //List of rhythms from XML
        private static List<Rhythm> m_rhythms = new List<Rhythm>();

        //Individual note arrays
        private ConcreteNote[] m_kickNotes;
        private ConcreteNote[] m_snareNotes;
        private ConcreteNote[] m_hiHatNotes;

        #endregion

        #region Properties

        /// <summary>
        /// Resolution of kick drum notes
        /// </summary>
        public int KickResolution
        {
            get
            {
                return m_kickResolution;
            }
            set
            {
                m_kickResolution = value;
            }
        }



        /// <summary>
        /// Resolution of snare drum notes
        /// </summary>
        public int SnareResolution
        {
            get
            {
                return m_snareResolution;
            }
            set
            {
                m_snareResolution = value;
            }
        }

        /// <summary>
        /// Resolution of high hat notes
        /// </summary>
        public int HiHatResolution
        {
            get
            {
                return m_highHatResolution;
            }
            set
            {
                m_highHatResolution = value;
            }
        }

        /// <summary>
        /// Modulo for kick list
        /// </summary>
        public int KickMod
        {
            get
            {
                return m_kickMod;
            }
            set
            {
                m_kickMod = value;
            }
        }

        /// <summary>
        /// Modulo for snare list
        /// </summary>
        public int SnareMod
        {
            get
            {
                return m_snareMod;
            }
            set
            {
                m_snareMod = value;
            }
        }

        /// <summary>
        /// Modulo for high hat list
        /// </summary>
        public int HighHatMod
        {
            get
            {
                return m_highHatMod;
            }
            set
            {
                m_highHatMod = value;
            }
        }

        /// <summary>
        /// List of rhythmic templates
        /// </summary>
        public static List<Rhythm> Rhythms
        {
            get
            {
                return m_rhythms;
            }
        }

        /// <summary>
        /// Rhythmic template to be used by phrase
        /// </summary>
        public Rhythm PhraseRhythm;

        #endregion

        #region Note list properties

        /// <summary>
        /// Array of kick drum notes
        /// </summary>
        public ConcreteNote[] KickNotes
        {
            get
            {
                return m_kickNotes;
            }
            set
            {
                m_kickNotes = value;
            }
        }

        /// <summary>
        /// Array of snare notes
        /// </summary>
        public ConcreteNote[] SnareNotes
        {
            get
            {
                return m_snareNotes;
            }
            set
            {
                m_snareNotes = value;
            }
        }

        /// <summary>
        /// Array of high hat notes
        /// </summary>
        public ConcreteNote[] HiHatNotes
        {
            get
            {
                return m_hiHatNotes;
            }
            set
            {
                m_hiHatNotes = value;
            }
        }

        #endregion

        #region MIDI properties

        #pragma warning disable 1591

        //Drum MIDI goes on out one channel, different note ids for each drum
        public static int KickNum = Int16.Parse(Resources.AppResources.KickNoteNumber);
        public static int KickOnMessageNumber = Convert.ToInt16(AppResources.DrumKitOnMessageID);
        public static int KickOffMessageNumber = Convert.ToInt16(AppResources.DrumKitOffMessageID);

        public static int SnareNum = Int16.Parse(Resources.AppResources.SnareNoteNumber);
        public static int SnareOnMessageNumber = Convert.ToInt16(AppResources.DrumKitOnMessageID);
        public static int SnareOffMessageNumber = Convert.ToInt16(AppResources.DrumKitOffMessageID);

        public static int HiHatNum = Int16.Parse(Resources.AppResources.HiHatNoteNumber);
        public static int HiHatOnMessageNumber = Convert.ToInt16(AppResources.DrumKitOnMessageID);
        public static int HiHatOffMessageNumber = Convert.ToInt16(AppResources.DrumKitOffMessageID);

        public static int SideStickNum = Int16.Parse(Resources.AppResources.SideStickNoteNumber);
        public static int SideStickOnMessageNumber = Convert.ToInt16(AppResources.DrumKitOnMessageID);
        public static int SideStickOffMessageNumber = Convert.ToInt16(AppResources.DrumKitOffMessageID);

        #pragma warning restore 1591

        #endregion

        #region Constructor

        /// <summary>
        /// Drum phrase constructor. Note that this contructor will need to be edited whenever new rhythms are added to the XML.
        /// </summary>
        /// <param name="progression">Progression that phrase is being attached to</param>
        /// <param name="basephrase">Phrase to use as template for generating notes</param>
        public DrumPhrase(ConcreteProgression progression, bool explode, DrumPhrase basephrase = null)
        {
            m_progression = progression;

            //If there is no basephrase, we are generating original note lists. Otherwise, initiate copying procedure
            if (explode)
                Commonality.Explode(this);
            else if (basephrase == null)
            {
                SelectRhythm();

                switch (PhraseRhythm.RhythmType)
                {
                    case 4:
                        Vibe.Instance.Generate(this);
                        break;
                    case 2:
                        Freestyle.Instance.Generate(this);
                        break;
                    default:
                        Rock.Instance.Generate(this);
                        break;
                }
            }
            else
            {
                PhraseRhythm = basephrase.PhraseRhythm;
                CopyToPhrase((ConcretePhrase)basephrase);
            }

            try
            {
                Progression.Notes.AddRange(m_notes);
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
        }

        #endregion

        #region Enums

        #pragma warning disable 1591

        public enum Type
        {
            Kick,
            Snare,
            HighHat
        }

        #pragma warning restore 1591

        #endregion

        #region Methods

        /// <summary>
        /// Load rhythms from XML
        /// </summary>
        public static void LoadRhythms()
        {
            XmlDocument doc = new XmlDocument();

            doc = new XmlDocument();
            doc.Load("C:\\Program Files\\Aleator\\Rhythm.xml");

            XmlNode root = doc.SelectSingleNode("/Rhythms");
            XmlNodeList nodeList = root.SelectNodes("Rhythm");

            foreach (XmlNode n in nodeList)
            {
                Rhythm r = new Rhythm();

                r.RhythmID = Convert.ToByte(n.SelectSingleNode("RhythmID").InnerText);
                r.RhythmType = Convert.ToByte(n.SelectSingleNode("RhythmType").InnerText);
                r.RhythmName = n.SelectSingleNode("RhythmName").InnerText;

                if (n.SelectSingleNode("MeasureWeight") != null)
                    r.MeasureWeight = Convert.ToByte(n.SelectSingleNode("MeasureWeight").InnerText);

                if (n.SelectSingleNode("PhraseWeight") != null)
                    r.PhraseWeight = Convert.ToByte(n.SelectSingleNode("PhraseWeight").InnerText);

                r.Distribution = n.SelectSingleNode("Distribution").InnerText;

                Rhythms.Add(r);
            }
        }

        //Select a rhythm from the XML list
        private void SelectRhythm()
        {
                int selector = Session.RndGenerator.Next(10);
                List<Rhythm> rhythmlist = new List<Rhythm>();

                //If the meter is even, give equal chance to every rhythm type other than sparse, which should be relatively seldom
                if (Progression.Numerator % 2 == 0)
                {
                    //30% chance of driving, 30% reggae, 20% bounce, 20% sparse
                    if (selector < 3)
                        rhythmlist = Rhythms.Where(r => r.RhythmType == 1).ToList();
                    else if (selector < 6)
                        rhythmlist = Rhythms.Where(r => r.RhythmType == 4).ToList();
                    else if (selector < 8)
                        rhythmlist = Rhythms.Where(r => r.RhythmType == 3).ToList();
                    else
                        rhythmlist = Rhythms.Where(r => r.RhythmType == 2).ToList();
                }
                //If meter is odd, 50% driving, 50% bounce
                else
                {
                    if (selector < 5)
                        rhythmlist = Rhythms.Where(r => r.RhythmType == 1).ToList();
                    else
                        rhythmlist = Rhythms.Where(r => r.RhythmType == 2).ToList();
                }

                PhraseRhythm = rhythmlist[rhythmlist.Count - 1];
        }

        /// <summary>
        /// Base one DrumPhrase on another
        /// </summary>
        /// <param name="p">The phrase we are copying from.</param>
        protected override void CopyToPhrase(ConcretePhrase p)
        {
            DrumPhrase dp = (DrumPhrase)p;
            this.KickResolution = dp.KickResolution;
            this.SnareResolution = dp.SnareResolution;
            this.HiHatResolution = dp.HiHatResolution;

            base.CopyToPhrase(p);
        }

        #endregion
    }
}
